package org.smartboot.compare.utils;

import org.smartboot.compare.FieldCache;
import org.smartboot.compare.GlobalConfiguration;
import org.smartboot.compare.MethodHandleFieldReader;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author qinluo
 * @version 1.0.0
 * @date 2021/2/4 1:43 上午
 */
@SuppressWarnings({"unchecked"})
public final class InternalClassUtils {

    // Primitive mapping wrapper class
    private static final Map<Class<?>, Class<?>> PMW = new HashMap<>(8);

    /**
     * Field Caches.
     */
    private static final Map<Class<?>, List<FieldCache>> FIELD_CACHE = new ConcurrentHashMap<>(128);

    static {
        internalPut(PMW, byte.class, Byte.class);
        internalPut(PMW, short.class, Short.class);
        internalPut(PMW, int.class, Integer.class);
        internalPut(PMW, long.class, Long.class);
        internalPut(PMW, float.class, Float.class);
        internalPut(PMW, double.class, Double.class);
        internalPut(PMW, char.class, Character.class);
        internalPut(PMW, boolean.class, Boolean.class);
    }

    private static void internalPut(Map<Class<?>, Class<?>> map, Class<?> left, Class<?> right) {
        map.put(left, right);
        map.put(right, left);
    }

    public static <T> Class<T> getWrapper(Class<?> p) {
        return (Class<T>) PMW.get(p);
    }

    public static boolean isSimple(Class<?> clz) {
        return clz.isPrimitive() || clz == Object.class || PMW.containsKey(clz);
    }

    public static boolean isCollection(Class<?> clz) {
        return List.class.isAssignableFrom(clz) || Map.class.isAssignableFrom(clz) || Set.class.isAssignableFrom(clz);
    }

    public static Class<?> lookupHighestType(Class<?> clz) {
        if (List.class.isAssignableFrom(clz)) {
            return List.class;
        } else if (Map.class.isAssignableFrom(clz)) {
            return Map.class;
        } else if (Set.class.isAssignableFrom(clz)) {
            return Set.class;
        }
        return clz;
    }

    public static List<FieldCache> getAllFields(Class<?> clz) {
        List<FieldCache> fields = FIELD_CACHE.get(clz);
        if (fields != null) {
            return fields;
        }

        List<Field> allFields = new ArrayList<>(128);
        Class<?> waitProcessClz = clz;
        while (waitProcessClz != Object.class) {
            addElements(allFields, waitProcessClz.getDeclaredFields());
            waitProcessClz = waitProcessClz.getSuperclass();
        }

        // filter static fields
        List<FieldCache> fieldCaches = allFields.stream().filter(p -> !Modifier.isStatic(p.getModifiers()) && !p.isSynthetic() && !Modifier.isTransient(p.getModifiers()))
                                    .map(FieldCache::new).collect(Collectors.toList());
        if (GlobalConfiguration.getFieldObjectUseMethodHandle) {
            Lookup lookup = MethodHandles.lookup();
            fieldCaches.forEach(p -> {
                try {
                    MethodHandle getter = lookup.findGetter(clz, p.getField().getName(), p.getField().getType());
                    if (getter != null) {
                        p.setReader(new MethodHandleFieldReader(getter));
                    }
                } catch (Throwable ignored) {

                }

            });
        }

        FIELD_CACHE.put(clz, fieldCaches);

        return fieldCaches;
    }

    private static <T> void addElements(List<T> list, T[] fields) {
        if (list == null || fields == null) {
            return;
        }
        Collections.addAll(list, fields);
    }
}
